O formato XML (sigla em inglês para “Extensible Markup Language”) surgiu ainda no final dos anos 1990, sendo um padrão aberto voltado à geração de documentos hierárquicos e que conta com uma ampla adesão por parte de sistemas computacionais dos mais variados tipos.
Do ponto de vista estrutural, o padrão XML possui vários pontos em comum com a linguagem HTML, estando baseados em marcações (tags). Contudo, diferente de HTML em que há um conjunto pré-definido destes elementos, o formato XML pode ser definido como uma metalinguagem: em termos práticos, isso significa que não existem tags pré-definidas, com esses itens podendo ser definidos conforme necessidades específicas.
Um documento XML é formado por um conjunto de elementos (nodes), com as relações de dependência entre estes elementos sendo estabelecidas por meio de níveis hierárquicos. Todo elemento é constituído por uma tag e seu respectivo conteúdo. Quanto àquilo que consta em um elemento, o mesmo pode estar vazio, possuir atributos ou ainda, conter um agrupamento de outros elementos. Já um atributo nada mais é do que um par formado por um nome identificador e um valor correspondente (este último entre aspas), com estes dois itens separados por um sinal de “=” (“igual”), além de terem sido declarados dentro da tag que define um elemento.
A estrutura flexível oferecida pelo padrão XML é utilizada em larga escala em processos de integração entre sistemas via Web Services, no conteúdo de sites da Internet, em dispositivos móveis etc. Atualmente, este formato é suportado pelas principais linguagens de programação e sistemas operacionais, existindo diversas técnicas para a manipulação de dados neste padrão.
Considerando o caso específico da plataforma .NET, diversos métodos permitem a implementação de funcionalidades baseadas na leitura e/ou escrita de informações no padrão XML. LINQ to XML é uma dessas tecnologias, oferecendo vários meios que viabilizam a realização de operações sobre dados hierárquicos. Além daquilo que é oferecido nativamente pela extensão LINQ to XML, outras funcionalidades podem ainda ser implementadas com base neste mecanismo, sem que isto implique necessariamente em herdar classes pré-existentes a fim de incluir ou, até mesmo, redefinir comportamentos: isto é possível graças ao uso de construções de código conhecidas como "Extension Methods".
O objetivo deste artigo é discutir como Extension Methods podem ser combinados a expressões que envolvam o uso de LINQ to XML, simplificando assim o processo de leitura de informações contidas em documentos XML.
Conteúdo do arquivo XML utilizado nos exemplos
Na Listagem 1 é apresentado o conteúdo do arquivo XML (Prestadores.xml) em que se baseiam os exemplos apresentados mais adiante.
No arquivo “Prestadores.xml” constam dados de prestadores de serviço contratados junto a uma hipotética Consultoria de Tecnologia. É possível constatar neste documento XML a presença dos seguintes campos:
- ID: Número inteiro que identifica um prestador de serviços;
- CPF: CPF do profissional;
- NomeProfissional: nome do prestador de serviços;
- Empresa: empresa da qual o prestador é proprietário;
- CNPJ: CNPJ da empresa prestadora de serviços;
- Cidade: cidade em que está aberta a empresa prestadora;
- Estado: estado da empresa prestadora;
- InscricaoEstadual: inscrição estadual da empresa prestadora de serviços (o conteúdo deste campo é opcional);
- ValorHora: valor recebido por hora trabalhada (preenchido quando o prestador de serviços ser pago de acordo como o número de horas trabalhadas);
- ValorMensal: valor recebido mensalmente (preenchido quando o prestador de serviços for pago de acordo com um valor fixo mensal);
- DataCadastro: data em que foi efetuado o cadastro do prestador de serviços;
- InicioAtividades: data em que o prestador de serviços iniciou suas atividades (o conteúdo deste campo é opcional).
<?xml version="1.0" encoding="utf-8"?> <Prestadores> <Prestador> <ID>131</ID> <CPF>111.111.111-11</CPF> <NomeProfissional>JOÃO DA SILVA</NomeProfissional> <Empresa>SILVA CONSULTORIA EM INFORMÁTICA LTDA</Empresa> <CNPJ>11.111.111/1111-11</CNPJ> <Cidade>São Paulo</Cidade> <Estado>SP</Estado> <InscricaoEstadual>1111-1</InscricaoEstadual> <ValorHora>50,00</ValorHora> <ValorMensal></ValorMensal> <DataCadastro>03/01/2013</DataCadastro> <InicioAtividades>03/01/2013</InicioAtividades> </Prestador> <Prestador> <ID>132</ID> <CPF>222.222.222-22</CPF> <NomeProfissional>JOAQUIM DE OLIVEIRA</NomeProfissional> <Empresa>SERVIÇOS DE TECNOLOGIA OLIVEIRA ME</Empresa> <CNPJ>22.222.222/2222-22</CNPJ> <Cidade>Belo Horizonte</Cidade> <Estado>MG</Estado> <InscricaoEstadual></InscricaoEstadual> <ValorHora></ValorHora> <ValorMensal>10527,35</ValorMensal> <DataCadastro>03/01/2013</DataCadastro> <InicioAtividades></InicioAtividades> </Prestador> <Prestador> <ID>133</ID> <CPF>333.333.333-33</CPF> <NomeProfissional>MARIA MARTINS</NomeProfissional> <Empresa>MARTINS TECNOLOGIA LTDA</Empresa> <CNPJ>33.333.333/3333-33</CNPJ> <Cidade>Rio de Janeiro</Cidade> <Estado>RJ</Estado> <InscricaoEstadual>33333</InscricaoEstadual> <ValorHora>65,00</ValorHora> <ValorMensal></ValorMensal> <DataCadastro>04/01/2013</DataCadastro> <InicioAtividades>10/01/2013</InicioAtividades> </Prestador> <Prestador> <ID>134</ID> <CPF>444.444.444-44</CPF> <NomeProfissional>JOANA SANTOS</NomeProfissional> <Empresa>CONSULTORIA SANTOS LTDA</Empresa> <CNPJ>44.444.444/4444-44</CNPJ> <Cidade>São Paulo</Cidade> <Estado>SP</Estado> <InscricaoEstadual></InscricaoEstadual> <ValorHora></ValorHora> <ValorMensal>8474,77</ValorMensal> <DataCadastro>04/01/2013</DataCadastro> <InicioAtividades></InicioAtividades> </Prestador> </Prestadores>
Definindo a classe que corresponde ao conteúdo do arquivo XML de Prestadores
A Listagem 2 apresenta a implementação da classe Prestador. Instâncias deste tipo equivalem, basicamente, aos dados contidos em cada elemento “Prestador” do documento XML de exemplo.
É possível notar ainda que campos opcionais dos tipos decimal e DateTime estão com um sinal de interrogação (“?”). Este recurso é conhecido como Nullable, permitindo a propriedades e variáveis que o utilizam armazenar também o valor null, por mais que estas tenham sido definidas a partir de um tipo primitivo (decimal) ou estruturado (DateTime). Sem o uso de uma Nullable tais referências assumiriam, se nenhum valor fosse associado às mesmas, o valor default para seus respectivos tipos (o número zero no caso de um decimal).
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TesteExtensionMethodsXML { public class Prestador { public int ID { get; set; } public string CPF { get; set; } public string NomeProfissional { get; set; } public string Empresa { get; set; } public string CNPJ { get; set; } public string Cidade { get; set; } public string Estado { get; set; } public string InscricaoEstadual { get; set; } public decimal? ValorHora { get; set; } public decimal? ValorMensal { get; set; } public DateTime DataCadastro { get; set; } public DateTime? InicioAtividades { get; set; } } }
Implementando a leitura do documento XML via LINQ to XML
A classe estática ArquivoPrestadores (Listagem 3) representa a estrutura responsável por efetuar a leitura do arquivo XML, a fim de com isto gerar uma coleção de objetos do tipo Prestador.
O processamento do documento XML e a posterior geração de instâncias da classe Prestador acontece através do método CarregarInformacoes:
- Inicialmente é gerada uma nova instância de XDocument (namespace System.Xml.Linq), a partir de uma chamada ao método Load, passando-se como parâmetro a este o nome do arquivo XML a ser carregado (parâmetro “arquivo”);
- Prossegue-se agora com a geração das instâncias baseadas na classe Prestador. Isso é feito através de um loop foreach, acionando-se para isto o método Element a partir da instância do tipo XDocument; esta primeira ação irá selecionar o node “Prestadores”. Em seguida, invoca-se o método Elements, o qual irá retornar como resultado uma coleção de referências da classe XElement (namespace System.Xml.Linq) e que correspondem aos elementos de nome “Prestador” (subordinados ao node principal “Prestadores”);
- A cada iteração do loop foreach é criada uma nova referência do tipo Prestador, atribuídos valores às propriedades deste objeto a partir de elementos presentes no node “Prestador” e, por fim, adicionada a instância em questão à coleção “prestadores” (a qual é retornada posteriormente como resultado da execução do método CarregarInformacoes);
- A leitura de cada elemento pertencente ao node “Prestador” se faz por meio do método Element, a partir da instância de XElement (variável “dadosPrestador”) declarada no início do loop foreach. Para isto, são invocados o método Element de “dadosPrestador” e, na sequência, a propriedade Value. Conforme é possível observar, o retorno da propriedade Value é sempre uma string, o que força a realização de procedimentos de conversão de um tipo para outro; além disso, verificações adicionais são realizadas quando se estiver manipulando um campo cujo conteúdo é opcional (caso das propriedades InscricaoEstadual, ValorHora, ValorMensal, DataCadastro e InicioAtividades).
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; using System.IO; namespace TesteExtensionMethodsXML { public static class ArquivoPrestadores { public static List<Prestador> CarregarInformacoes( string arquivo) { List<Prestador> prestadores = new List<Prestador>(); Prestador prestador; string strInscrEstadual; string strValorHora; decimal valorHora; string strValorMensal; decimal valorMensal; string strDataCadastro; DateTime dataCadastro; string strInicioAtividades; DateTime inicioAtividades; XDocument xml = XDocument.Load(arquivo); foreach (XElement dadosPrestador in xml.Element("Prestadores").Elements("Prestador")) { prestador = new Prestador(); prestador.ID = Convert.ToInt32(dadosPrestador .Element("ID").Value); prestador.CPF = dadosPrestador .Element("CPF").Value; prestador.Empresa = dadosPrestador .Element("Empresa").Value; prestador.NomeProfissional = dadosPrestador .Element("NomeProfissional").Value; prestador.CNPJ = dadosPrestador .Element("CNPJ").Value; prestador.Cidade = dadosPrestador .Element("Cidade").Value; prestador.Estado = dadosPrestador .Element("Estado").Value; strInscrEstadual = dadosPrestador .Element("InscricaoEstadual").Value; if (!String.IsNullOrWhiteSpace(strInscrEstadual)) prestador.InscricaoEstadual = strInscrEstadual; strValorHora = dadosPrestador. Element("ValorHora").Value; if (!String.IsNullOrWhiteSpace(strValorHora) && decimal.TryParse(strValorHora, out valorHora)) prestador.ValorHora = valorHora; strValorMensal = dadosPrestador.Element("ValorMensal").Value; if (!String.IsNullOrWhiteSpace(strValorMensal) && decimal.TryParse(strValorMensal, out valorMensal)) prestador.ValorMensal = valorMensal; strDataCadastro = dadosPrestador .Element("DataCadastro").Value; if (!String.IsNullOrWhiteSpace(strDataCadastro) && DateTime.TryParse(strDataCadastro, out dataCadastro)) prestador.DataCadastro = dataCadastro; strInicioAtividades = dadosPrestador .Element("InicioAtividades").Value; if (!String.IsNullOrWhiteSpace( strInicioAtividades) && DateTime.TryParse(strInicioAtividades, out inicioAtividades)) prestador.InicioAtividades = inicioAtividades; prestadores.Add(prestador); } return prestadores; } } }
Já na Listagem 4 está um exemplo de trecho de código que emprega a classe ArquivoPrestadores, fornecendo para isto o caminho em que se encontra o arquivo “Prestadores.xml”.
... List<Prestador> prestadores = ArquivoPrestadores .CarregarInformacoes(@"C:\Devmedia\Prestadores.xml"); ...
Por mais que a implementação da classe ArquivoPrestadores seja simples e não exija grandes esforços, a leitura e a conversão de valores originários de um arquivo XML pode gerar trechos extensos de código, bem como se repetir ao longo de uma aplicação. É neste ponto que o recurso conhecido como Extension Methods pode se revelar como extremamente útil, conforme será demonstrado na próxima seção. Este mecanismo possibilita não apenas um código mais enxuto, como também facilitando o uso de funcionalidades básicas para tratamento de dados no formato XML.
Utilizando Extension Methods
O recurso conhecido como Extension Methods foi disponibilizado a partir da versão 3.5 do .NET Framework. Em termos práticos, um Extension Method nada mais é do que um método estático, muito embora um desenvolvedor possa enxergar o mesmo como se fosse uma operação acessível a partir da instância de uma determinada classe.
Este tipo de construção evita a necessidade de se gerar uma nova classe, servindo como um meio para “adicionar” novas funcionalidades a um recurso já existente. O próprio .NET Framework emprega de forma extensiva essa prática: LINQ é um bom exemplo de mecanismo que faz uso de Extension Methods, de maneira a facilitar com isto a manipulação de coleções de objetos através de métodos como Where e OrderBy.
Na Listagem 5 encontra-se a implementação da classe estática LinqToXmlExtensions. Este tipo foi estruturado da seguinte forma:
- O método estático GetStringValue recebe como parâmetros uma instância do tipo XElement, além do nome de um node que pertença a este elemento. Caso tal node exista e possua um valor associado ao mesmo, será retornada a string correspondente; do contrário, o método GetStringValue devolverá como resultado de sua execução o valor null;
- As operações estáticas StringValue, IntValue, DecimalValue e DateTimeValue representam exemplos de Extension Methods, adicionando novas funcionalidades ao tipo XElement, sem porém exigir a necessidade de se gerar uma nova classe a partir do mesmo. Todos esses métodos recebem como parâmetro uma instância da classe XElement precedida pela palavra-chave “this” (isto é uma característica típica de um Extension Method), além de um segundo parâmetro com o nome do node a ser lido;
- Os métodos StringValue, IntValue, DecimalValue e DateTimeValue acionam a operação GetStringValue, manipulando os valores fornecidos por esta última e devolvendo resultados baseados em seus respectivos tipos de retorno. No caso específico de IntValue, DecimalValue e DateTimeValue isto envolve a utilização de valores Nullable e da classe Convert em transformações de dados.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; namespace TesteExtensionMethodsXML { public static class LinqToXmlExtensions { private static string GetStringValue( XElement element, string node) { string valor = null; XElement childElement = element.Element(node); if (childElement != null) { valor = childElement.Value; if (!String.IsNullOrWhiteSpace(valor)) valor = valor.Trim(); else valor = null; } return valor; } public static string StringValue( this XElement element, string node) { return GetStringValue(element, node); } public static int? IntValue( this XElement element, string node) { string valor = GetStringValue(element, node); if (valor != null) return Convert.ToInt32(valor); else return null; } public static decimal? DecimalValue( this XElement element, string node) { string valor = GetStringValue(element, node); if (valor != null) return Convert.ToDecimal(valor); else return null; } public static DateTime? DateTimeValue( this XElement element, string node) { string valor = GetStringValue(element, node); if (valor != null) return Convert.ToDateTime(valor); else return null; } } }
A simples definição da classe LinqToXmlExtensions em um projeto de testes fará com que o próprio IntelliSense do Visual Studio liste os métodos que estendem uma classe. É o que pode ser observado na Figura 1 com o método IntValue ou ainda, com a operação StringValue conforme demonstrado na Figura 2.
Na Listagem 6 são apresentadas algumas instruções da classe ArquivoPrestador já modificadas. Nota-se que o uso dos métodos IntValue, StringValue, DecimalValue e DateTimeValue simplificou consideravelmente a implementação do método CarregarInformacoes.
... prestador.ID = dadosPrestador.IntValue("ID").Value; prestador.CPF = dadosPrestador.StringValue("CPF"); prestador.Empresa = dadosPrestador.StringValue("Empresa"); ... prestador.InscricaoEstadual = dadosPrestador.StringValue("InscricaoEstadual"); prestador.ValorHora = dadosPrestador.DecimalValue("ValorHora"); prestador.ValorMensal = dadosPrestador.DecimalValue("ValorMensal"); prestador.DataCadastro = dadosPrestador.DateTimeValue("DataCadastro").Value; prestador.InicioAtividades = dadosPrestador.DateTimeValue("InicioAtividades"); ...
Já a Listagem 7 demonstra outra implementação possível para a classe ArquivoPrestadores, combinando para isto o uso de uma consulta LINQ em conjunto com as classes XDocument e XElement, além dos Extension Methods definidos anteriormente através da classe LinqToXmlExtensions.
Esta nova versão da classe ArquivoPrestadores constitui um bom exemplo de como o uso de Extension Methods pode contribuir para uma codificação menos extensa. Por meio deste recurso conseguiu-se, basicamente, evitar a escrita de longos trechos de código visando a leitura e o tratamento de dados no formato XML. Importante frisar que os métodos definidos em LinqToXmlExtensions também poderiam ser facilmente reutilizados em outros pontos de uma aplicação hipotética.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; using System.IO; namespace TesteExtensionMethodsXML { public static class ArquivoPrestadores { public static List<Prestador> CarregarInformacoes( string arquivo) { XDocument xml = XDocument.Load(arquivo); return (from XElement dadosPrestador in xml .Element("Prestadores").Elements("Prestador") select new Prestador() { ID = dadosPrestador .IntValue("ID").Value, CPF = dadosPrestador .StringValue("CPF"), Empresa = dadosPrestador .StringValue("Empresa"), NomeProfissional = dadosPrestador .StringValue("NomeProfissional"), CNPJ = dadosPrestador .StringValue("CNPJ"), Cidade = dadosPrestador .StringValue("Cidade"), Estado = dadosPrestador .StringValue("Estado"), InscricaoEstadual = dadosPrestador .StringValue("InscricaoEstadual"), ValorHora = dadosPrestador .DecimalValue("ValorHora"), ValorMensal = dadosPrestador .DecimalValue("ValorMensal"), DataCadastro = dadosPrestador .DateTimeValue("DataCadastro").Value, InicioAtividades = dadosPrestador .DateTimeValue("InicioAtividades") }).ToList(); } } }
Conclusão
Procurei com este artigo demonstrar como LINQ e o recurso conhecido como Extension Methods pode simplificar bastante a manipulação de arquivos XML. A definição de métodos que estendam as capacidades de tipos como XElement evita não apenas a criação de diversas subclasses (algo que provavelmente aumentaria a complexidade de um projeto), como também possibilita o reuso de determinadas funcionalidades por toda uma aplicação (ou mesmo em diversas soluções de software).
Espero que o conteúdo aqui apresentado possa ser útil no seu dia-a-dia.
Até uma próxima oportunidade!